/*
	Class: Attributes
	
		The Attributes class is used to store the attributes and their values 
		of a DocumentElement or of a rule set in a stylesheet. The attribute
		"style" is treated in a special way.
		
		As a stylesheet rule set, an instance of this class is a dispatcher of
		change events. The listeners of these events or those instances actually
		associated with a DocumentElement. When a change event is dispatched,
		its listeners will re-parse their attributes (and styles) and re-render
		themselves if necessary.
		
		XXX In the case of of re-parsing after a change, changes to the "custom" 
		attribute are ignored. Ultimately, it could destroy the current 
		container and re-construct it.
		
		We track our attributes in the attributes Object. Each value in
		attributes is an object with the fields: name, value, isOriginal, and
		originalValue. The point of the isOriginal flag is to support our
		re-parsing process. In that process, we give our original attributes a
		specificity of 1,0,0 and then order it along with any matching
		stylesheet selectors. We then apply all the associated styles in order.
		Re-parsing occurs due to changes in stylesheets and our attributes.
		
		We alter the original XML node and its attributes so that queries on the
		XML document will reflect these changes. All changes to the XML node
		should be through this object though so that we can track them and react 
		to them.
*/	
package com.frontalcode 
{
	import flash.events.*;
	import flash.utils.*;
	import flash.display.*;
	
	/**
	 * The Attributes class is used to store the attributes and their values 
	 * of a DocumentElement or of a rule set in a stylesheet. The attribute
	 * "style" is treated in a special way with the Style class.
	 */
    public class Attributes extends OptionableObject
	{
		/**
		 * The StylesManager instance this object is associated with.
		 */
		public var manager : StylesManager;
		
		/**
		 * The DocumentElement instance this object is associated with or null.
		 */
		public var container : DocumentElement;
		
		/**
		 * The Styles instance associated with this object to handle the special
		 * "style" attribute.
		 */
		public var styles : Styles;
		
		/*
			Variable: selectors
			
				An array of stylesheet selectors that match our container's 
				node. Each entry is an Object with members selector and 
				constraints. constraints is an Object built in 
				StylesheetSelector with members hover, ... and matched which
		*/
		/**
		 * @private
		 */
		internal var selectors : Array = [ ];

		private var selectorsSorted : Boolean = false;
		
		/*
			Variable: selectorConstraints
			
				This Dictionary is used to store the constraints associated with
				a StylesheetSelector and is keyed by the entries in <selectors>. 
				Each entry is an Object that holds all of the constraining 
				DocumentElements for the selector as calculated in 
				StylesheetSelector.
		*/
		/**
		 * @private
		 */
		internal var selectorConstraints : Dictionary = new Dictionary ( );
		
		/*
			Variable: namespaces
			
				An object containing all the namespaces for which there are 
				attributes for this container's node. The keys for this Object
				are namespace URIs and the values are Objects to store the 
				individual attributes in that namespace. For example:
				
				namespaces = {
						namespace1: {
								attribute1: {
										value: *,
										touched: Boolean,		// used to determine if this attr. is still valid
										isOriginal: Boolean,	// true if part of the baseline set of attr.s
										originalValue: *
									}
								...
							}
						...
					}
					
				Note that if this Attributes object is not associated with a
				DocumentElement then the namespaces keys may in fact be 
				namespace prefixes and not URIs. This is because without a node,
				the prefixes cannot be resolved. This is fine as such Attributes
				are used to apply attributes and styles to DocumentElements and
				at that time the namespace prefixes will be resolved.
		*/
		/**
		 * @private
		 */
		internal var namespaces : Object = { }; 
		
		/*
			Variable: namespaceChanges
			
				Track any changes to our attributes. We will listen to our 
				container for its render event to clear this change log.
				
				XXX The idea here is that attribute changes may have varying
				impacts on performance. So having a log of changes since the
				last render gives us an opportunity to optimize these impacts.
				But we currently do not take advantage of this.
		*/
		/**
		 * @private
		 */
		internal var namespaceChanges : Object = { };

		/*
			Variables: stylesheetOrder
			
				This variable is only populated if we are associated with a
				DocumentElement (container != null). In that case, we treat our
				our "style" attribute like its own stylesheet at least in terms
				of ordering them wrt our associated selectors..
		*/
		/**
		 * @private
		 */
		internal var stylesheetOrder : Number;
		
		/*
			Variables: initialStylesApplied
			
				Whether or not styles have been applied can affect the way we 
				tween our style changes.
		*/
		/**
		 * @private
		 */
		internal var initialStylesApplied : Boolean = false;

		/**
		 * Creates a new Attributes instance. This constructor is not typically
		 * called directly as every DocumentElement has an instance constructed
		 * in its own constructor and accessible via its attributes property.
		 * 		
		 * @param manager	The document-specific StylesManager.
		 * @param container	The DocumentElement whose attributes are being 
		 *					maintained or null if this is not associated with
		 *					a DocumentElement.
		 * @param options	Options affecting the behavior of this instance.
		 * 					(This object does not have built-in support for any
		 * 					options.)
		 */	
		public function Attributes ( manager : StylesManager, container : DocumentElement = null, options : Object = null )
		{
			super ( options );
			
			this.manager = manager;
			this.container = container;
			
			if ( container != null ) 
			{
				stylesheetOrder = StylesheetParser.getNextStylesheetOrderKey ( );
				parseNode ( container.node );
				parseAttributes ( );
			}
			
			styles = new Styles ( manager, this );
		}
		
		/**
		 * @private
		 */
		internal function getNamespaceURI ( namespace : * = null ) : String
		{
			var nsURI : Namespace;
			if ( namespace is Namespace )
			{
				nsURI = namespace;
			}
			else if ( namespace is String && namespace != null && namespace != "" )
			{
				if ( container != null ) nsURI = container.node.namespace ( namespace );
				
				if ( nsURI == null )
				{
					// When container is null, this is really the namespace 
					// prefix but when its applied to an Attributes object that 
					// is associated with a DocumentElement then it will be 
					// resolved again into the actual namespace.
					//
					// If container is not null but nsURI is then this is 
					// either an unregistered prefix or is already the URI.
					//
					nsURI = new Namespace ( namespace );
				}
			}
			return nsURI == null ? "" : nsURI.toString ( );
		}
		
		/**
		 * Gets the value of an attribute.
		 */
		public function getAttribute ( name : String, namespace : * = null ) : *
		{
			// Like with the XML class, undefined and null attributes return an
			// empty string.
			//
			var nsName : String = getNamespaceURI ( namespace );
			var attributes : Object = namespaces [ nsName ];
			if ( attributes == null ) return "";
			return attributes [ name ] != null && attributes [ name ].value != null ? attributes [ name ].value : "";
		}
		
		/**
		 * Determines if an attribute exists or not.
		 */
		public function attributeExists ( name : String, namespace : String = null ) : Boolean
		{
			var nsName : String = getNamespaceURI ( namespace );
			var attributes : Object = namespaces [ nsName ];
			if ( attributes == null ) return false;
			return attributes [ name ] != null && attributes [ name ].value !== undefined;
		}
		
		/**
		 * Get all the namespaces for which there are attributes.
		 */
		public function getNamespaces ( ) : Array
		{
			var result : Array = [ ];
			for ( var namespace : String in namespaces ) result.push ( namespace );
			return result;
		}
		
		/**
		 * Get all the attributes (regardless of namespace).
		 */
		public function getAttributes ( namespace : String = null ) : Array
		{
			var result : Array = [ ];
			var nsName : String = getNamespaceURI ( namespace );
			var attributes : Object = namespaces [ nsName ];
			for ( var attribute : String in attributes ) result.push ( attribute );
			return result;
		}
		
		/*
			Function: setAttribute
			
				Set an attribute.
				
				By specifying a value of undefined, an attribute can be removed.
				
			Parameters:
			
				name				- the name of the attribute
				value				- use undefined to remove an attribute
				namespace			- the XML namespace of the attribute
				doesNotNeedRender	- do not mark our container as needing rendering
				isOriginal			- is this from the original parsing of the node?
		*/	
		/**
		 * Set an attribute.
		 */
		public function setAttribute ( name : String, value : *, namespace : * = null, doesNotNeedRender : Boolean = false, isOriginal : Boolean = false ) : void
		{
			var nsName : String = getNamespaceURI ( namespace );
			if ( namespaces [ nsName ] == null ) namespaces [ nsName ] = { };
			var attributes : Object = namespaces [ nsName ];
			if ( attributes [ name ] == null ) attributes [ name ] = { name: name };
			attributes [ name ].touched = true;
			var valueChanged : Boolean = false;
			if ( attributes [ name ].value != value )
			{
				valueChanged = true;
				
				if ( namespaceChanges [ nsName ] == null ) namespaceChanges [ nsName ] = { };
				namespaceChanges [ nsName ] [ name ] = attributes [ name ];
				
				var qualifiedName : * = nsName == "" ? name : new QName ( nsName, name );
				if ( value === undefined )
				{
					if ( container != null ) delete container.node.@ [ qualifiedName ];
				}
				else
				{
					if ( container != null ) container.node.@ [ qualifiedName ] = value;
				}

				// Index this element in the document.
				//
				if ( container != null && ( qualifiedName == "id" || qualifiedName == "class" ) )
				{
					var currentIdValue		: String = attributes.hasOwnProperty ( "id" )    && attributes [ "id" ].value != null		? attributes [ "id" ].value		: "";
					var currentClassValue	: String = attributes.hasOwnProperty ( "class" ) && attributes [ "class" ].value != null	? attributes [ "class" ].value	: "";
					
					container.document.unindexDocumentElement ( container.node, currentIdValue, currentClassValue );
					
					var idValue		: String = qualifiedName == "id"	? value : currentIdValue;
					var classValue	: String = qualifiedName == "class"	? value : currentClassValue;
					
					container.document.indexDocumentElement ( container.node, idValue, classValue );
				}
				
				attributes [ name ].value = value;
			}
			
			var originalChanged : Boolean = false;
			if ( isOriginal )
			{
				originalChanged = attributes [ name ].originalValue != value;
				attributes [ name ].isOriginal = true;
				attributes [ name ].originalValue = value;
			}
			
			// If we have a container, let it know it might need to be rendered
			// after the attribute changes.
			//
			if ( container != null && ! doesNotNeedRender && valueChanged ) container.needsRender = true;
			
			// If we're turning off delayParsing after it having been on then we
			// parse our container's node.
			//
			if ( container != null && container.unparsedChildrenNodes != null && name == "delayParsing" && originalChanged && ! Util.isTrue.test ( value ) )
			{
				// We're ready to parse these children nodes now.
				//
				var unparsedChildrenNodes : XMLList = container.unparsedChildrenNodes;
				container.unparsedChildrenNodes = null;
				container.document.insertXML ( unparsedChildrenNodes, container );
			}
			
			// XXX Also an attribute change may affect whether we match a 
			// selector or not. This means running _every_ selector's match 
			// routine whenever an attribute changes. Theoretically, we'd like
			// to run them against just this node but can we run a selector's
			// match routine against a single node? We at least need its
			// ancestors for the match.
			//
			// We also want to be careful about infinite loops. That is, if 
			// we're applying an attribute because we're applying a stylesheet
			// rule, then we can't re-parse ourselves or we'll just start over
			// again.
			//
			// And of course there's the case that applying one stylesheet rule
			// adds an attribute that causes the rule not to match but does 
			// cause another rule to be matched which cause the addition of an
			// attribute that causes the original rule to match again.
			//
			// Bottomline? For now attribute changes do not cause selectors to
			// be reapplied.
			//
		}
		
		/*
			Function: parseAttributes
			
				(Re)build our attributes.
		*/
		/**
		 * @private
		 */
		internal function parseAttributes ( event : FrontalEvent = null ) : void
		{
			if ( container != null )
			{
				// Apply any stylesheet rule sets. They are sorted based on 
				// their specificity and creation order. Styles assigned via the
				// style attribute (our "original" styles) always come first as 
				// described here:
				//
				// http://www.w3.org/TR/CSS21/cascade.html#specificity
				//
				var newNamespaces : Object = { };
				applyOriginalAttributes ( newNamespaces );
				
				sortSelectors ( );

				for each ( var selector : StylesheetSelector in selectors ) 
				{
					// Only apply the declarations of those selectors whose 
					// constraints are currently matched.
					//
					if ( selectorConstraints [ selector ].match )
					{
						for ( var namespace : String in selector.attributes.namespaces )
						{
							for ( var attributeName : String in selector.attributes.namespaces [ namespace ] )
							{
								if ( newNamespaces [ namespace ] == null || ! newNamespaces [ namespace ].hasOwnProperty ( attributeName ) )
								{
									if ( newNamespaces [ namespace ] == null ) newNamespaces [ namespace ] = { };
									newNamespaces [ namespace ] [ attributeName ] = selector.attributes.namespaces [ namespace ] [ attributeName ].value;
								}
							}
						}
						
						addListener ( selector.attributes, FrontalEvent.CHANGE, parseAttributes, false, 0, true );
					}
				}
				
				untouchAttributes ( );
				for ( namespace in newNamespaces )
				{
					for ( attributeName in newNamespaces [ namespace ] )
					{
						setAttribute ( attributeName, newNamespaces [ namespace ] [ attributeName ], namespace );
					}
				}
				deleteUntouchedAttributes ( );
			}
		}
		
		internal function sortSelectors ( ) : void
		{
			if ( ! selectorsSorted )
			{
				selectorsSorted = true;
				selectors.sort ( StylesheetSelector.compareSelectors );
			}
		}
		
		private function applyOriginalAttributes ( newNamespaces : Object ) : void
		{
			for ( var namespace : String in namespaces )
			{
				for each ( var attribute : Object in namespaces [ namespace ] )
				{
					if ( attribute.isOriginal && ( newNamespaces [ namespace ] == null || ! newNamespaces [ namespace ].hasOwnProperty ( attribute.name ) ) ) 
					{
						if ( newNamespaces [ namespace ] == null ) newNamespaces [ namespace ] = { };
						newNamespaces [ namespace ] [ attribute.name ] = attribute.originalValue;
					}
				}
			}
		}
		
		/**
		 * @private
		 */
		internal function reparse ( ) : void
		{
			parseAttributes ( );
			styles.parseStyles ( );
			initialStylesApplied = true;
		}
		
		/*
			Function: parseNode
			
				Parse the attributes in an XML node. This process adds to / 
				overwrites our current attributes.
				
			Parameters:
			
				node		- The XML node to parse.
				isOriginal	- Indicates the parsed values should be added to our baseline state.
		*/
		/**
		 * @private
		 */
		internal function parseNode ( node : XML, isOriginal : Boolean = true ) :void
		{
			for ( var i : uint = 0; i < node.attributes ( ).length ( ); i++ ) 
			{
				setAttribute ( node.attributes ( ) [ i ].localName ( ), String ( node.attributes ( ) [ i ] ), node.attributes ( ) [ i ].namespace ( ), false, isOriginal );
			}
		}
		
		/*
			Function: untouchAttributes
			
				We un-touch all of our attributes. This is in preparation for
				making mass changes during which we'll touch each attribute we
				set. After making our changes, we'll remove any attributes that
				were not touched.
		*/
		/**
		 * @private
		 */
		internal function untouchAttributes ( ) : void
		{
			for ( var namespace : String in namespaces )
			{
				for each ( var attribute : Object in namespaces [ namespace ] )
				{
					attribute.touched = false;
				}
			}
		}
		
		/**
		 * @private
		 */
		internal function deleteUntouchedAttributes ( ) : void
		{
			for ( var namespace : String in namespaces )
			{
				for each ( var attribute : Object in namespaces [ namespace ] )
				{
					if ( ! attribute.touched ) 
					{
						setAttribute ( attribute.name, undefined, namespace );
						if ( ! attribute.isOriginal ) 
						{
							delete namespaces [ namespace ] [ attribute.name ];
							var nsIsEmpty : Boolean = true;
							for ( var key : String in namespaces [ namespace ] )
							{
								nsIsEmpty = false;
								break;
							}
							if ( nsIsEmpty ) delete namespaces [ namespace ];
						}
					}
				}
			}
		}
		
		/**
		 * @private
		 */
		internal function clearChanges ( ) : void
		{
			namespaceChanges = { };
		}
		
		internal function hasChanges ( ) : Boolean
		{
			for ( var i : * in namespaceChanges )
			{
				for ( var j : * in namespaceChanges [ i ] )
				{
					return true;
				}
			}
			
			return false;
		}
		
		internal function attributeChanged ( attribute : String, namespace : * = null ) : Boolean
		{
			var nsName : String = getNamespaceURI ( namespace );
			return namespaceChanges [ nsName ] != null && namespaceChanges [ nsName ] [ attribute ] != null;
		}
		
		/*
			Function: registerSelector
			
				This method allows a selector to let this instance know that it 
				has been matched. As a result we will mark our container as 
				needing to be rendered. During that process, we will go through
				all the registered selectors and apply its declarations.
				
			Parameters:
			
				selector		- The matching stylesheet selector.
				constraintsList	- Various elements that match pseudoclasses in 
								  the selector. We'll listen to these elements 
								  for changes which will trigger re-matching the 
								  selector. 
		*/
		/**
		 * @private
		 */
		internal function registerSelector ( selector : StylesheetSelector, constraintsList : Object ) : void
		{
			var index : int = selectors.indexOf ( selector );
			if ( index < 0 ) 
			{
				selectorsSorted = false;
				selectors.push ( selector );
				selectorConstraints [ selector ] = constraintsList;
				
				if ( container != null ) applyConstraints ( selector );
				
				// Support the ability to broadcast information through a 
				// selector to all matching DocumentElements.
				//
				addListener ( selector, FrontalEvent.NOTIFICATION, selectorNotificationHandler );
			}
		}
		
		/**
		 * @private
		 */
		internal function unregisterSelector ( selector : StylesheetSelector ) : void
		{
			var index : int = selectors.indexOf ( selector );
			if ( index >= 0 ) 
			{	
				// XXX We should also remove any listeners we have on its 
				// declarations.
				//
				// XXX We should also remove any listeners we've added as part
				// of monitoring this selector's constraints. We need to be 
				// careful though because the same listener may have been
				// applied for constraints on two different selectors.
				// 
				
				removeListener ( selectors [ index ], FrontalEvent.NOTIFICATION, selectorNotificationHandler );
				delete selectorConstraints [ selectors [ index ] ];
				selectors.splice ( index, 1 );
				if ( container != null ) container.needsParse = true;
			}
		}
		
		/**
		 * @private
		 */
		internal function selectorNotificationHandler ( event : FrontalEvent ) : void
		{
			if ( container != null ) container.runInteractions ( "onSelectorNotification", { event: event } );
		}
		
		/*
			Function: applyConstraints
			
				Constraints are created by pseudo-classes in the
				StylesheetSelector. The selector should not be applied unless
				and until all of the constraints have been passed. Some
				constraints are dynamic (like hover and focus) and some simply
				refer to the state of a DocumentElement (like visited and
				disabled). Here we add listeners to the DocumentElement and its
				movie to track changes that might affect whether or not our
				constraints match. In the handler for these events, if a 
				constraint match changes, e.g., it goes from being unmatched to
				being matched, then we reparse our attributes and styles.
				
				XXX Where do these listeners get removed? They don't!
		*/
		
		static private var dynamicPseudoClassDefns : Object = {
				hover:	{ enter: MouseEvent.ROLL_OVER,	exit: MouseEvent.ROLL_OUT },
				active:	{ enter: MouseEvent.MOUSE_DOWN,	exit: MouseEvent.MOUSE_UP },
				focus:	{ enter: FocusEvent.FOCUS_IN,	exit: FocusEvent.FOCUS_OUT }
			};
		
		/**
		 * @private
		 */
		internal function applyConstraints ( selector : StylesheetSelector ) : void
		{
			// Each entry in selectorConstraints [ selector ] looks like this:
			//
			//	{
			//		match: false,
			//		constraints:
			//			[
			//				{
			//					pseudoClassName: String,
			//					pseudoClassValue: undefined | String,
			//					match: false,
			//					docElem: DocumentElement
			//				},
			//				...
			//			]
			//	}
			//
			var constraintsWrapper : Object = selectorConstraints [ selector ];
			var prevMatch : Boolean = constraintsWrapper.match;
			constraintsWrapper.match = true;
			
			for each ( var constraint : Object in constraintsWrapper.constraints )
			{	
				if ( dynamicPseudoClassDefns [ constraint.pseudoClassName ] != null )
				{
					constraint.match = false;
					
					if ( constraint.docElem is Container )
					{
						addListener ( constraint.docElem.movie, dynamicPseudoClassDefns [ constraint.pseudoClassName ].enter, constraintHandler );
						if ( constraint.pseudoClassName != "active" )
						{
							// For the active state, we will listen for the MOUSE_UP
							// event on the stage so that we get an event even if
							// the mouse buttons comes up while the pointer is not
							// over the target.
							//
							addListener ( constraint.docElem.movie, dynamicPseudoClassDefns [ constraint.pseudoClassName ].exit, constraintHandler );
						}
					}
				}
				else
				{
					constraint.match = true;
					
					switch ( constraint.pseudoClassName )
					{
						case "link":
							if ( ! ( constraint.docElem is Container ) ) break;
							if ( ! constraint.docElem.isLink || constraint.docElem.isVisited ) constraint.match = false;
							addListener ( constraint.docElem, FrontalEvent.LINK_CHANGE, constraintHandler );
							break;
							
						case "visited":
							if ( ! ( constraint.docElem is Container ) ) break;
							if ( ! constraint.docElem.isLink || ! constraint.docElem.isVisited ) constraint.match = false;
							addListener ( constraint.docElem, FrontalEvent.VISITED_CHANGE, constraintHandler );
							break;
							
						case "disabled":
							constraint.match = ! constraint.docElem.enabled;
							addListener ( constraint.docElem, FrontalEvent.ENABLED_CHANGE, constraintHandler );
							break;
							
						case "selected":
							constraint.match = constraint.docElem.selected;
							addListener ( constraint.docElem, FrontalEvent.SELECTED_CHANGE, constraintHandler );
							break;
							
						default:
							// Handle custom pseudo classes.
							//
							var currentValue : * = constraint.docElem.getPseudoClassState ( constraint.pseudoClassName );
							if ( ( constraint.pseudoClassValue == null && ! currentValue ) || ( constraint.pseudoClassValue != null && currentValue != constraint.pseudoClassValue ) ) constraint.match = false;
							addListener ( constraint.docElem, FrontalEvent.PSEUDO_CLASS_STATE_CHANGE, constraintHandler );
							break;
					}
				}
				
				if ( constraint.negate ) constraint.match = ! constraint.match;
				if ( ! constraint.match ) constraintsWrapper.match = false;
			}
			
			if ( prevMatch != constraintsWrapper.match ) 
			{
				selector.runActions ( constraintsWrapper.match, container );
				container.needsParse = true;
			}
		}
		
		/*
			Step through each selector seeing if its constraints have been
			passed or failed.
			
			XXX Do we need to do absolute tests for ROLL_OVER (like with
			hitTest) or will we get that even simply by adding the event
			listener to a MovieClip with the mouse over it already? We assume
			the latter here and it seems to be working.
		*/
		/**
		 * @private
		 */
		internal function constraintHandler ( event : Event ) : void
		{
			var doReparse : Boolean = false;
			
			if ( event.type == MouseEvent.MOUSE_DOWN )
			{
				// This is turning on the active state. We want to turn it off
				// with a mouse up anywhere which means listening to the stage.
				//
				addListener ( ( event.target as DisplayObject ).stage, MouseEvent.MOUSE_UP, constraintHandler );
				addListener ( ( event.target as DisplayObject ).stage, MouseEvent.MOUSE_UP, constraintHandler, true );
			}
			else if ( event.type == MouseEvent.MOUSE_UP )
			{
				// Remove the stage listeners.
				//
				removeListener ( ( event.target as DisplayObject ).stage, MouseEvent.MOUSE_UP, constraintHandler );
				removeListener ( ( event.target as DisplayObject ).stage, MouseEvent.MOUSE_UP, constraintHandler, true );
			}

			for ( var key : Object in selectorConstraints )
			{
				if ( ( key as StylesheetSelector ).selector.otherAttrsAndPseudoClasses == 0 ) continue;
				
				var constraintsWrapper : Object = selectorConstraints [ key ];
				var match : Boolean = true;
				
				// Remember, not all constraintsWrappers can be checked in a
				// static fashion. That is, constraint.match is what turns 
				// events into a state. That is why we can't just use the local
				// match variables and loop over each entry and expect correct
				// results.
				// 
				for each ( var constraint : Object in constraintsWrapper.constraints )
				{	
					if ( dynamicPseudoClassDefns [ constraint.pseudoClassName ] == null )
					{
						constraint.match = true;
						
						switch ( constraint.pseudoClassName )
						{
							case "link":
								if ( ! ( constraint.docElem is Container ) ) break;
								if ( ! constraint.docElem.isLink || constraint.docElem.isVisited ) constraint.match = false;
								break;
								
							case "visited":
								if ( ! ( constraint.docElem is Container ) ) break;
								if ( ! constraint.docElem.isLink || ! constraint.docElem.isVisited ) constraint.match = false;
								break;
								
							case "disabled":
								if ( constraint.docElem.enabled ) constraint.match = false;
								break;
								
							case "selected":
								if ( ! constraint.docElem.selected ) constraint.match = false;
								break;
								
							default:
								// Handle custom pseudo classes.
								//
								var currentValue : * = constraint.docElem.getPseudoClassState ( constraint.pseudoClassName );
								if ( ( constraint.pseudoClassValue == null && ! currentValue ) || ( constraint.pseudoClassValue != null && currentValue != constraint.pseudoClassValue ) ) constraint.match = false;
								break;
						}
						
						if ( constraint.negate ) constraint.match = ! constraint.match;
					}
					else
					{
						if ( 
								constraint.docElem is Container &&
								( constraint.docElem.movie === event.currentTarget || 
								  ( constraint.pseudoClassName == "active" && event.type == MouseEvent.MOUSE_UP ) 
								) &&
								( event.type == dynamicPseudoClassDefns [ constraint.pseudoClassName ].enter || 
								  event.type == dynamicPseudoClassDefns [ constraint.pseudoClassName ].exit 
								) 
							) 
						{
							// We have an event relevant to our dynamic pseudo 
							// class.
							//
							constraint.match = event.type == dynamicPseudoClassDefns [ constraint.pseudoClassName ].enter;
							if ( constraint.negate ) constraint.match = ! constraint.match;
						}
					}
					
					if ( ! constraint.match ) match = false;
				}

				if ( match != constraintsWrapper.match )
				{
					doReparse = true;
					constraintsWrapper.match = match;
					( key as StylesheetSelector ).runActions ( constraintsWrapper.match, container );
				}
			}
			
			// Do not set needsParse... this is much faster.
			// XXX Analyze the performance of this.
			//
			if ( doReparse ) 
			{
				reparse ( );
			}
		}
	}
}

